﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Pg.BioMedics.SDR.Caching.Linq
{
    /// <summary>
    /// LINQ expression processor implementing visitor pattern. This processaor transforms 
    /// a LINQ expression tree into cache parameters set.
    /// </summary>
    internal static class ExpressionProcessor
    {
        #region Extension methods

        /// <summary>
        /// Processes expression tree defined by the root expression element.
        /// </summary>
        /// <typeparam name="T">Cache entity type</typeparam>
        /// <param name="expression">Expression</param>
        /// <returns>Collection of tag parameters for cache query</returns>
        internal static IEnumerable<string[]> ProcessExpression<T>(this Expression expression)
        {
            if (expression.NodeType == ExpressionType.Call)
                return ProcessCall<T>((MethodCallExpression)expression);
            else if (expression.NodeType == ExpressionType.Equal)
                return new List<string[]>() { new string[] { ProcessEqual<T>((BinaryExpression)expression)} };
            else if (expression.NodeType == ExpressionType.AndAlso)
                return new List<string[]>() { ProcessAndAlso<T>((BinaryExpression)expression).ToArray() };
            else if (expression.NodeType == ExpressionType.OrElse)
                return ProcessOrElse<T>((BinaryExpression)expression);
            else if (expression.NodeType == ExpressionType.Quote)
                return ProcessExpression<T>(((UnaryExpression)expression).Operand);
            else if (expression.NodeType == ExpressionType.Lambda)
                return ProcessExpression<T>(((LambdaExpression)expression).Body);
            else
                return new List<string[]>();
        }

        #endregion

        #region Support methods

        /// <summary>
        /// Processes call expression
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression">Call expression</param>
        /// <returns>Collection of tag parameters for the cache query</returns>
        private static IList<string[]> ProcessCall<T>(MethodCallExpression expression)
        {
            IList<string[]> parameters = new List<string[]>();
            foreach (Expression subExpression in expression.Arguments)
                foreach (string[] tags in ProcessExpression<T>(subExpression))
                    parameters.Add(tags);

            return parameters;
        }        

        /// <summary>
        /// Processes AndAlso expression
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression">AndAlso logical expression</param>
        /// <returns>List of tags extracted from operands. This collection will be used for performing atomic select operation</returns>
        private static IList<string> ProcessAndAlso<T>(BinaryExpression expression)
        {
            List<string> tags = new List<string>();
            if (expression.Left.NodeType == ExpressionType.AndAlso)
                tags.AddRange(ProcessAndAlso<T>((BinaryExpression)expression.Left));
            if (expression.Left.NodeType == ExpressionType.Equal)
                tags.Add(ProcessEqual<T>((BinaryExpression)expression.Left));

            if (expression.Right.NodeType == ExpressionType.AndAlso)
                tags.AddRange(ProcessAndAlso<T>((BinaryExpression)expression.Right));
            if (expression.Right.NodeType == ExpressionType.Equal)
                tags.Add(ProcessEqual<T>((BinaryExpression)expression.Right));

            return tags;
        }

        /// <summary>
        /// Processes OrElse expression
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression">OrElse logical expression</param>
        /// <returns>List of tags parameter. A query will be a sum of query results for each tags parameter</returns>
        private static IList<string[]> ProcessOrElse<T>(BinaryExpression expression)
        {
            List<string[]> parameters = new List<string[]>();
            if (expression.Left.NodeType == ExpressionType.OrElse)
                parameters.AddRange(ProcessOrElse<T>((BinaryExpression)expression.Left));
            else if (expression.Left.NodeType == ExpressionType.AndAlso)
                parameters.Add(ProcessAndAlso<T>((BinaryExpression)expression.Left).ToArray());

            if (expression.Right.NodeType == ExpressionType.OrElse)
                parameters.AddRange(ProcessOrElse<T>((BinaryExpression)expression.Right));
            else  if (expression.Right.NodeType == ExpressionType.AndAlso)
                parameters.Add(ProcessAndAlso<T>((BinaryExpression)expression.Right).ToArray());

            return parameters;
        }

        /// <summary>
        /// Processes Member expression
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression">Member expression</param>
        /// <returns>Member description utulized by the tag</returns>
        private static object ProcessMember<T>(MemberExpression expression)
        {
            if (((ParameterExpression)expression.Expression).Type == typeof(T))
                return expression.Member.Name;
            else
                return null;
        }

        private static string ProcessConvert<T>(UnaryExpression expression)
        {
            if (expression.Operand.NodeType == ExpressionType.Constant)
                return Convert.ToString(((ConstantExpression)expression.Operand).Value);
            else
                return null;
        }

        /// <summary>
        /// Processes equality expression. This method extracts left and right operands from 
        /// the equality expression and returns a transcription into single tag of this equality
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression">Binary equality expression</param>
        /// <returns>Tag value of equality expression</returns>
        private static string ProcessEqual<T>(BinaryExpression expression)
        {
            string property = null;
            string value = null;

            if (expression.Left.NodeType == ExpressionType.Constant)
                value = Convert.ToString(((ConstantExpression)expression.Left).Value);
            else if (expression.Left.NodeType == ExpressionType.Convert)
                value = ProcessConvert<T>((UnaryExpression)expression.Left);
            else if (expression.Left.NodeType == ExpressionType.MemberAccess)
                property = (string)ProcessMember<T>((MemberExpression)expression.Left);

            if (expression.Right.NodeType == ExpressionType.Constant)
                value = Convert.ToString(((ConstantExpression)expression.Right).Value);
            else if (expression.Right.NodeType == ExpressionType.Convert)
                value = ProcessConvert<T>((UnaryExpression)expression.Right);
            else if (expression.Right.NodeType == ExpressionType.MemberAccess)
                property = (string)ProcessMember<T>((MemberExpression)expression.Left);

            if ((!String.IsNullOrEmpty(property)) && (!String.IsNullOrEmpty(value)))
                return property + DistributedCachingStrategy<T>.TagSeparator + value;
            else
                return null;
        }

        #endregion
    }
}
